from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# 获取当前脚本的绝对路径，并找到父目录
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
# 将父目录添加到 Python 模块搜索路径
sys.path.append(parent_dir)
# Import the backtrader platform
import backtrader as bt

class TestStrategy(bt.Strategy): 
    def log(self, txt, dt=None): 
        # 记录策略的执行日志  
        dt = dt or self.datas[0].datetime.date(0) 
        print('%s, %s' % (dt.isoformat(), txt)) 

    def __init__(self): 
        # 保存收盘价的引用  
        self.dataclose = self.datas[0].close
        self.dataopen = self.datas[0].open
        # 跟踪挂单  
        self.order = None 
        # 买入价格和手续费  
        self.buyprice = None 
        self.buycomm = None 
        
    # 订单状态通知，买入卖出都是下单  
    def notify_order(self, order): 
        if order.status in [order.Submitted, order.Accepted]:
            # broker 提交/接受了，买/卖订单则什么都不做  
            return 
        # 检查一个订单是否完成  
        # 注意: 当资金不足时，broker会拒绝订单  
        if order.status in [order.Completed]: 
            if order.isbuy(): 
                self.log( 
                    '已买入, 价格: %.2f, 费用: %.2f, 佣金 %.2f' % 
                    (order.executed.price, 
                    order.executed.value, 
                    order.executed.comm)) 
                self.buyprice = order.executed.price 
                self.buycomm = order.executed.comm 
            elif order.issell(): 
                self.log('已卖出, 价格: %.2f, 费用: %.2f, 佣金 %.2f' % 
                    (order.executed.price, 
                    order.executed.value, 
                    order.executed.comm)) 
            # 记录买入时的K线位置 
            self.bar_executed = len(self) 

        elif order.status in [order.Canceled, order.Margin, order.Rejected]: 
            self.log('订单取消/保证金不足/拒绝')

        # 其他状态记录为：无挂起订单  
        self.order = None 


    # 交易状态通知，一买一卖算交易  
    def notify_trade(self, trade): 
        if not trade.isclosed: 
            return 
        self.log('交易利润, 毛利润 %.2f, 净利润 %.2f' % 
            (trade.pnl, trade.pnlcomm)) 

    def next(self): 
        # 记录收盘价  
        self.log('Open, %.2f, Close, %.2f' % (self.dataopen[0], self.dataclose[0])) 
        # 如果有订单正在挂起，不操作  
        if self.order: 
            return 
    # 如果没有持仓则买入  
        if not self.position: 
            # 今天的收盘价 < 昨天收盘价  
            if self.dataclose[0] < self.dataclose[-1]: 
                # 昨天收盘价 < 前天的收盘价  
                if self.dataclose[-1] < self.dataclose[-2]: 
                    # 买入  
                    self.log('买入单, %.2f' % self.dataclose[0]) 
                    # 跟踪订单避免重复  
                    self.order = self.buy() 
        else: 
            # 如果已经持仓，且当前交易数据量在买入后5个单位后  
            if len(self) >= (self.bar_executed + 5): 
                # 全部卖出  
                self.log('卖出单, %.2f' % self.dataclose[0]) 
                # 跟踪订单避免重复  
                self.order = self.sell()

if __name__ == '__main__':
    # 创建Cerebro引擎  
    cerebro = bt.Cerebro() 
    # Cerebro引擎在后台创建broker(经纪人)，系统默认资金量为10000 
    # 为Cerebro引擎添加策略  
    cerebro.addstrategy(TestStrategy) 
    # 获取当前运行脚本所在目录  
    modpath = os.path.dirname(os.path.abspath(sys.argv[0])) 
    # 拼接加载路径  
    datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt') 
    # 创建交易数据集  
    data = bt.feeds.YahooFinanceCSVData( 
                                    dataname=datapath, 
                                    # 数据必须大于fromdate 
                                    fromdate=datetime.datetime(2000, 1, 1), 
                                    # 数据必须小于todate 
                                    todate=datetime.datetime(2000, 12, 31), 
                                    reverse=False) 
    # 加载交易数据  
    cerebro.adddata(data) 
    # 设置投资金额100000.0 
    cerebro.broker.setcash(100000.0) 
    cerebro.broker.setcommission(commission=0.001) # 0.001即是0.1% ,让我们设定一个常见的费率0.1%，买卖都要收
    # 引擎运行前打印期出资金  
    print('组合期初资金: %.2f' % cerebro.broker.getvalue()) 
    cerebro.run() 
    # 引擎运行后打印期末资金  
    print('组合期末资金: %.2f' % cerebro.broker.getvalue())

"""
(1) order.executed.price
含义：订单的 实际成交价格。
单位：标的资产的价格单位（如股票的单位是每股价格）。
示例：
若以市价单买入股票，实际成交价为 100 元/股，则 order.executed.price = 100。
若订单分多次成交（部分成交），此值为 最后一次成交的价格。

(2) order.executed.value
含义：订单的 实际成交金额（总成本或总收益）。
单位：账户的现金单位（如美元、人民币）。

order.executed.value = (
    order.executed.price * order.executed.size       # 基础金额
    + order.executed.comm                            # 手续费
    + ...                                            # 其他费用（如滑点、税费）
)

(1) 买入订单（手续费为固定比例）
假设策略执行以下操作：
买入 100 股
成交价格：50 元/股
手续费率：0.1%（按成交金额计算）
计算结果：
executed.price = 50
基础金额：50 * 100 = 5000 元
手续费：5000 * 0.001 = 5 元
executed.value = 5000 + 5 = 5005 元

(2) 卖出订单（含滑点）
假设策略执行以下操作：
卖出 200 股
预期价格：30 元/股
实际成交价格（因滑点损失）：29.8 元/股
手续费率：0.1%
计算结果：
executed.price = 29.8
基础金额：29.8 * 200 = 5960 元
手续费：5960 * 0.001 = 5.96 元
executed.value = 5960 - 5.96 = 5954.04 元（卖出时手续费通常从收益中扣除）

(1) 计算持仓成本
def notify_order(self, order):
    if order.status == order.Completed:
        if order.isbuy():
            # 买入后更新平均成本价
            self.avg_cost = (
                (self.avg_cost * self.position.size) + order.executed.value
            ) / (self.position.size + order.executed.size)
(2) 统计交易费用
def notify_order(self, order):
    if order.status == order.Completed:
        # 累加总手续费
        self.total_commission += order.executed.comm
        # 计算净收益：value 已包含手续费
        self.net_pnl += order.executed.value - (order.executed.price * order.executed.size)
(3) 风险控制（限制单笔交易金额）
def next(self):
    if self.data.close[0] * 100 > self.broker.getvalue() * 0.1:
        # 单笔交易金额不超过总资金的10%
        return
    self.order = self.buy(size=100)
"""

"""
notify_order与notify_trade的区别：
1. notify_order：订单状态通知
功能
监听对象：订单（Order）的生命周期状态变化。
触发条件：订单状态发生变更时触发，例如：
订单提交（Submitted）
订单被经纪商接受（Accepted）
订单完成（Completed）
订单部分成交（Partial）
订单取消（Canceled）
保证金不足（Margin）
订单过期（Expired）
典型用途
跟踪订单是否成功执行。
记录订单的成交价格、成交量、手续费等细节。
在订单失败时发出警告或调整策略逻辑。

notify_order：监控订单执行细节，处理订单级事件。

2. notify_trade：交易生命周期通知
功能
监听对象：交易（Trade）的生命周期状态变化。
触发条件：交易状态发生变更时触发，例如：
交易创建（开仓）
交易更新（持仓数量变化）
交易关闭（平仓）
典型用途
跟踪持仓的盈亏、持续时间、最大回撤等。
在交易关闭时统计总收益或生成报告。

notify_trade：跟踪交易整体表现，处理交易级事件。

def notify_trade(self, trade):
    if trade.isclosed:  # 交易平仓
        self.log(f'交易结束, 总盈亏: {trade.pnl:.2f}, 持仓天数: {trade.dtopen} 至 {trade.dtclose}')
    elif trade.isopen:  # 交易开仓
        self.log(f'交易开仓, 价格: {trade.price:.2f}, 数量: {trade.size}')

一个完整的交易（如买入后卖出）涉及两个订单（buy 和 sell），每个订单的状态变化会触发 notify_order。
交易的生命周期（开仓到平仓）通过 notify_trade 跟踪。

用户调用 self.buy() 创建买入订单。
订单被接受时触发 notify_order 的 Accepted 状态。
订单成交后触发 notify_order 的 Completed 状态，并自动创建交易。
交易开仓触发 notify_trade 的 isopen 事件。
用户调用 self.sell() 平仓，订单完成后触发 notify_order，交易关闭触发 notify_trade 的 isclosed 事件。

"""